pub use super::_entities::catalogs::{self, ActiveModel, Entity, Model};
pub(crate) use crate::models::_entities::catalogs::CreateCatalogParams;
use loco_rs::prelude::*;
use serde::Deserialize;
use uuid::Uuid;


#[derive(Debug, Validate, Deserialize)]
pub struct Validator {
    #[validate(length(min = 0, message = "Parent ID must be valid."))]
    pub parent_id: String,
}

impl Validatable for ActiveModel {
    fn validator(&self) -> Box<dyn Validate> {
        Box::new(Validator {
            parent_id: self.parent_id.as_ref().clone(),
        })
    }
}

#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
    async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        self.validate()?;
        if insert {
            let mut this = self;
            this.id = ActiveValue::Set(Uuid::new_v4().to_string());
            Ok(this)
        } else {
            Ok(self)
        }
    }
}

impl Model {
    /// finds a catalog by the provided id
    ///
    /// # Errors
    ///
    /// When could not find catalog or DB query error
    pub async fn find_by_id(db: &DatabaseConnection, id: &str) -> ModelResult<Self> {
        let catalog = catalogs::Entity::find()
            .filter(
                model::query::condition()
                    .eq(catalogs::Column::Id, id)
                    .build(),
            )
            .one(db)
            .await?;
        catalog.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// finds a catalog by the provided slug
    ///
    /// # Errors
    ///
    /// When could not find catalog or DB query error
    pub async fn find_by_slug(db: &DatabaseConnection, slug: &str) -> ModelResult<Self> {
        let catalog = catalogs::Entity::find()
            .filter(
                model::query::condition()
                    .eq(catalogs::Column::Slug, slug)
                    .build(),
            )
            .one(db)
            .await?;
        catalog.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// finds catalogs by the provided parent id
    ///
    /// # Errors
    ///
    /// When could not find catalogs or DB query error
    pub async fn find_by_parent_id(
        db: &DatabaseConnection,
        parent_id: &str,
    ) -> ModelResult<Vec<Self>> {
        let catalogs = catalogs::Entity::find()
            .filter(
                model::query::condition()
                    .eq(catalogs::Column::ParentId, parent_id)
                    .build(),
            )
            .all(db)
            .await?;
        Ok(catalogs)
    }

    /// finds root catalogs (catalogs without parent)
    ///
    /// # Errors
    ///
    /// When could not find catalogs or DB query error
    pub async fn find_roots(db: &DatabaseConnection) -> ModelResult<Vec<Self>> {
        let catalogs = catalogs::Entity::find()
            .filter(
                model::query::condition()
                    .is_null(catalogs::Column::ParentId)
                    .build(),
            )
            .all(db)
            .await?;
        Ok(catalogs)
    }

    /// Asynchronously creates a catalog and saves it to the database.
    ///
    /// # Errors
    ///
    /// When could not save the catalog into the DB
    pub async fn create(db: &DatabaseConnection, params: &CreateCatalogParams) -> ModelResult<Self> {
        let txn = db.begin().await?;

        if let Some(slug) = &params.slug {
            if catalogs::Entity::find()
                .filter(
                    model::query::condition()
                        .eq(catalogs::Column::Slug, slug)
                        .build(),
                )
                .one(&txn)
                .await?
                .is_some()
            {
                return Err(ModelError::EntityAlreadyExists {});
            }
        }

        let catalog = ActiveModel {
            parent_id: ActiveValue::set(params.parent_id.clone()),
            slug: ActiveValue::set(params.slug.clone()),
            position: ActiveValue::set(params.position),
            active: ActiveValue::set(params.active.unwrap_or_default()),
            ..Default::default()
        }
        .insert(&txn)
        .await?;

        txn.commit().await?;

        Ok(catalog)
    }
}

impl ActiveModel {
    /// Updates the catalog's active status
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_active_status(
         self,
        db: &DatabaseConnection,
        active: bool,
    ) -> ModelResult<Model> {
        let mut am: ActiveModel = self.into();
        am.active = ActiveValue::set(active);
        Ok(am.update(db).await?)
    }

    /// Updates the catalog's position
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_position(
         self,
        db: &DatabaseConnection,
        position: i32,
    ) -> ModelResult<Model> {
        let mut am: ActiveModel = self.into();
        am.position = ActiveValue::set(position);
        Ok(am.update(db).await?)
    }

    /// Updates the catalog's parent
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_parent(
         self,
        db: &DatabaseConnection,
        parent_id: String,
    ) -> ModelResult<Model> {
        let mut am: ActiveModel = self.into();
        am.parent_id = ActiveValue::set(parent_id);
        Ok(am.update(db).await?)
    }
}
